/*
    vga driver
    Copyright (C) 2011  Jiabo <jiabo2011@gmail.com>

    All functions copied from qemu 0.13 or 0.5.1 vga.c

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

//#define TEST_FB

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#ifdef TEST_FB
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <fcntl.h>
#include <sys/mman.h>
#endif
#include "../global.h"
#include "../ioport.h"
#include "vga.h"

//#define DEBUG_VGA

#ifdef DEBUG_VGA
#define DPRINTF(fmt, ...)                                       \
    do { printf("VGA: " fmt , ## __VA_ARGS__); } while (0)
#else
#define DPRINTF(fmt, ...)
#endif

#define CONFIG_S3VGA

#define MSR_COLOR_EMULATION 0x01

/* force some bits to zero */
const uint8_t sr_mask[8] = {
	0x03,
	0x3d,
	0x0f,
	0x3f,
	0x0e,
	0x00,
	0x00,
	0xff,
};

const uint8_t gr_mask[16] = {
    0x0f, /* 0x00 */
    0x0f, /* 0x01 */
    0x0f, /* 0x02 */
    0x1f, /* 0x03 */
    0x03, /* 0x04 */
    0x7b, /* 0x05 */
    0x0f, /* 0x06 */
    0x0f, /* 0x07 */
    0xff, /* 0x08 */
    0x00, /* 0x09 */
    0x00, /* 0x0a */
    0x00, /* 0x0b */
    0x00, /* 0x0c */
    0x00, /* 0x0d */
    0x00, /* 0x0e */
    0x00, /* 0x0f */
};

static int vga_ioport_invalid(VGACommonState *s, uint32_t addr)
{
	if (s->msr & MSR_COLOR_EMULATION) {
		/* Color */
		return (addr >= 0x3b0 && addr <= 0x3bf);
	} else {
		/* Monochrome */
		return (addr >= 0x3d0 && addr <= 0x3df);
	}
}

uint32_t vga_ioport_read(void *opaque, uint32_t addr)
{
	VGACommonState *s = opaque;
	int val/*, index*/;


	if (vga_ioport_invalid(s, addr)) {
		val = 0xff;
	} else {
		switch(addr) {
		case 0x3ba:
		case 0x3da:
			/* just toggle to fool polling */
			s->ar_flip_flop = 0;
			break;
		}
	}
	DPRINTF("read addr=0x%04x data=0x%02x\n", addr, val);

	return val;
}

static void vga_ioport_write(void *opaque, uint32_t addr, uint32_t val)
{
	VGACommonState *s = opaque;
	int index, v;

	DPRINTF("write addr=0x%04x data=0x%02x\n", addr, val);

	/* check port range access depending on color/monochrome mode */
	if (vga_ioport_invalid(s, addr)) {
		return;
	}

	switch(addr) {
	case 0x3c0:
		if (s->ar_flip_flop == 0) {
			val &= 0x3f;
			s->ar_index = val;
		} else {
			index = s->ar_index & 0x1f;
			switch(index) {
			case 0x00 ... 0x0f:
				s->ar[index] = val & 0x3f;
				break;
			case 0x10:
				s->ar[index] = val & ~0x10;
				break;
			case 0x11:
				s->ar[index] = val;
				break;
			case 0x12:
				s->ar[index] = val & ~0xc0;
				break;
			case 0x13:
				s->ar[index] = val & ~0xf0;
				break;
			case 0x14:
				s->ar[index] = val & ~0xf0;
				break;
			default:
				break;
			}
		}
		s->ar_flip_flop ^= 1;
		break;
	case 0x3c2:
		s->msr = val & ~0x10;
		break;
	case 0x3c4:
		s->sr_index = val & 7;
		break;
        case 0x3c5:
		DPRINTF("write SR%x = 0x%02x\n", s->sr_index, val);
		s->sr[s->sr_index] = val & sr_mask[s->sr_index];
		break;
	case 0x3c7:
		s->dac_read_index = val;
		s->dac_sub_index = 0;
		s->dac_state = 3;
		break;
	case 0x3c8:
		s->dac_write_index = val;
		s->dac_sub_index = 0;
		s->dac_state = 0;
		break;
	case 0x3c9:
		s->dac_cache[s->dac_sub_index] = val;
		if (++s->dac_sub_index == 3) {
			memcpy(&s->palette[s->dac_write_index * 3], s->dac_cache, 3);
			s->dac_sub_index = 0;
			s->dac_write_index++;
		}
		break;
	case 0x3ce:
		s->gr_index = val & 0x0f;
		break;
	case 0x3cf:
		DPRINTF("vga: write GR%x = 0x%02x\n", s->gr_index, val);
		s->gr[s->gr_index] = val & gr_mask[s->gr_index];
#if 0
		if (s->gr_index == 6) { //Miscellaneous Graphics Register
			switch ((val >> 2) & 3) {
			case 0:
				s->vram_ptr = 0xa0000;
				s->vram_size = 128 * 1024;
				break;
			case 1:
				s->vram_ptr = 0xa0000;
				s->vram_size = 64 * 1024;
				break;
			case 2:
				s->vram_ptr = 0xb0000;
				s->vram_size = 32 * 1024;
				break;
			case 3:
				s->vram_ptr = 0xb8000;
				s->vram_size = 32 * 1024;
				break;
			}
			printf("set vga ram base %p, size %08x\n", s->vram_ptr, s->vram_size);
		}
#endif
		break;
	case 0x3b4:
	case 0x3d4:
		s->cr_index = val;
		break;
	case 0x3b5:
	case 0x3d5:
		DPRINTF("vga: write CR%x = 0x%02x\n", s->cr_index, val);
		/* handle CR0-7 protection */
		if ((s->cr[11] & 0x80) && s->cr_index <= 7) {
			/* can always write bit 4 of CR7 */
			if (s->cr_index == 7)
			s->cr[7] = (s->cr[7] & ~0x10) | (val & 0x10);
			return;
		}
		switch(s->cr_index) {
		case 0x01: /* horizontal display end */
		case 0x07:
		case 0x09:
		case 0x0c:
		case 0x0d:
		case 0x12: /* veritcal display end */
			s->cr[s->cr_index] = val;
			break;
#ifdef CONFIG_S3VGA
		/* S3 registers */
		case 0x2d:
		case 0x2e:
		case 0x2f:
		case 0x30:
			/* chip ID, cannot write */
			break;
		case 0x31:
			/* update start address */
			s->cr[s->cr_index] = val;
			v = (val >> 4) & 3;
			s->cr[0x69] = (s->cr[69] & ~0x03) | v;
			break;
		case 0x51:
			/* update start address */
			s->cr[s->cr_index] = val;
			v = val & 3;
			s->cr[0x69] = (s->cr[69] & ~0x0c) | (v << 2);
			break;
#endif
		default:
			s->cr[s->cr_index] = val;
			break;
		}
#ifdef CONFIG_S3VGA
        if (s->cr_index >= 0x20)
            DPRINTF("S3: CR write index=0x%x val=0x%x\n",
                   s->cr_index, val);
#endif
        break;
	}
}
#ifdef TEST_FB
uint8_t *fbp;
void fb_init(void)
{
	int fbfd;
	struct fb_var_screeninfo vinfo;
	int screensize = 0;

	fbfd = open("/dev/fb0", O_RDWR);
	if (fbfd < 0) {
		printf("open error!\n");
		exit(-1);
	}
	printf("step1: test ioctl\n");
	ioctl(fbfd, FBIOGET_VSCREENINFO,&vinfo);
	printf("vinfo.xres = %d, vinfo.yres = %d, vinfo.bits_per_pixel = %d\n",
		vinfo.xres, vinfo.yres, vinfo.bits_per_pixel);
	screensize = vinfo.xres*vinfo.yres * vinfo.bits_per_pixel / 8;
	printf ("screensize = %d\n", screensize);
	fbp = mmap(0, screensize, PROT_READ|PROT_WRITE, MAP_SHARED, fbfd, 0);
//	memset(fbp, 0x0, screensize);
}
#endif
VGACommonState *vga_init(uint8_t *vga_ram_base)
{
	VGACommonState *s;
	s = calloc(1, sizeof(VGACommonState));
	if (s == NULL) {
		printf("vga init error\n");
		exit(-1);
	}

	s->vram_ptr = vga_ram_base;

	register_ioport_write(0x3c0, 16, 1, vga_ioport_write, s);
	
	register_ioport_write(0x3b4, 2, 1, vga_ioport_write, s);
	register_ioport_write(0x3d4, 2, 1, vga_ioport_write, s);
	register_ioport_write(0x3ba, 1, 1, vga_ioport_write, s);
	register_ioport_write(0x3da, 1, 1, vga_ioport_write, s);

	register_ioport_read(0x3c0, 16, 1, vga_ioport_read, s);

	register_ioport_read(0x3b4, 2, 1, vga_ioport_read, s);
	register_ioport_read(0x3d4, 2, 1, vga_ioport_read, s);
	register_ioport_read(0x3ba, 1, 1, vga_ioport_read, s);
	register_ioport_read(0x3da, 1, 1, vga_ioport_read, s);

#ifdef TEST_FB
	fb_init();
#endif
	return s;
}

static void vga_get_text_resolution(VGACommonState *s, int *pwidth, int *pheight,
                                    int *pcwidth, int *pcheight)
{
	int width, cwidth, height, cheight;

	/* total width & height */
	cheight = (s->cr[9] & 0x1f) + 1;
	cwidth = 8;
	if (!(s->sr[1] & 0x01))
		cwidth = 9;
	if (s->sr[1] & 0x08)
		cwidth = 16; /* NOTE: no 18 pixel wide */
	width = (s->cr[0x01] + 1);
	if (s->cr[0x06] == 100) {
		/* ugly hack for CGA 160x100x16 - explain me the logic */
		height = 100;
	} else {
		height = s->cr[0x12] |
			((s->cr[0x07] & 0x02) << 7) |
			((s->cr[0x07] & 0x40) << 3);
		height = (height + 1) / cheight;
	}

	*pwidth = width;
	*pheight = height;
	*pcwidth = cwidth;
	*pcheight = cheight;
}

static const uint32_t dmask4[4] = {
	0x00000000,
	0x0000ffff,
	0xffff0000,
	0xffffffff,
};

#ifdef TEST_FB
static void vga_draw_glyph9_32(uint8_t *d, int linesize,
                                          const uint8_t *font_ptr, int h, 
                                          uint32_t fgcol, uint32_t bgcol, int dup9)
{
	uint32_t font_data, xorcol, v;

	xorcol = bgcol ^ fgcol;
	do {
		font_data = font_ptr[0];
		/* XXX: unaligned accesses are done */
		((uint32_t *)d)[0] = ((-(font_data >> 7)) & xorcol) ^ bgcol;
		((uint32_t *)d)[1] = ((-((font_data >> 6) & 1)) & xorcol) ^ bgcol;
		((uint32_t *)d)[2] = ((-((font_data >> 5) & 1)) & xorcol) ^ bgcol;
		((uint32_t *)d)[3] = ((-((font_data >> 4) & 1) & xorcol)) ^ bgcol;
		((uint32_t *)d)[4] = ((-((font_data >> 3) & 1) & xorcol)) ^ bgcol;
		((uint32_t *)d)[5] = ((-((font_data >> 2) & 1) & xorcol) )^ bgcol;
		((uint32_t *)d)[6] = ((-((font_data >> 1) & 1) & xorcol)) ^ bgcol;
		v = ((-(font_data >> 0) & 1) & xorcol) ^ bgcol;
		((uint32_t *)d)[7] = v;
		if (dup9)
			((uint32_t *)d)[8] = v;
		else
			((uint32_t *)d)[8] = bgcol;
		font_ptr ++;
		d += linesize;
	} while (--h);
}
#endif

/*static */void vga_draw_text(VGACommonState *s)
{
	uint32_t v;
	int width, height, cwidth, cheight, cx, cy;
	uint32_t start_addr, offset;
	uint8_t *src, *dst;
	uint16_t ch_attr;
	uint16_t *last_ch_attr;
	char ch;
	uint8_t cattr;
	const uint8_t *font_ptr, *font_base[2];

	/* compute font data address (in plane 2) */
	v = s->sr[3];
	offset = (((v >> 4) & 1) | ((v << 1) & 6)) * 8192 * 4;
	if (offset != s->font_offsets[0]) {
		s->font_offsets[0] = offset;
	}
	font_base[0] = phys_ram_base + 0xa0000 + offset;

	offset = (((v >> 5) & 1) | ((v >> 1) & 6)) * 8192 * 4;
	if (offset != s->font_offsets[1]) {
		s->font_offsets[1] = offset;
	}
	font_base[1] = phys_ram_base + 0xa0000 + offset;
	printf("font offset 0 is: 0x%08x\n", s->font_offsets[0]);
	printf("font offset 1 is: 0x%08x\n", s->font_offsets[1]);
	vga_get_text_resolution(s, &width, &height, &cwidth, &cheight);
	printf("whole size: %d*%d\n", width, height);
	printf("character size: %d*%d\n", cwidth, cheight);

	/* starting address */
	start_addr = s->cr[0x0d] | (s->cr[0x0c] << 8);
#ifdef CONFIG_S3VGA
	start_addr |= (s->cr[0x69] & 0x1f) << 16; /* S3 extension */
#endif
	printf("start address: 0x%08x\n", start_addr);

#ifdef TEST_FB
	dst = fbp;
#endif
	src = s->vram_ptr + start_addr;
	last_ch_attr = s->last_ch_attr;
	for (cy = 0; cy < height; cy++) {
		for (cx = 0; cx < width; cx++) {
			ch_attr = *(uint16_t *)src;
			if (ch_attr != *last_ch_attr) {
				*last_ch_attr = ch_attr;
				ch = ch_attr & 0xff;
				cattr = ch_attr >> 8;
				font_ptr = font_base[(cattr >> 3) & 1];
				font_ptr += 32 * ch;
//				bgcol = palette[cattr >> 4];
//				fgcol = palette[cattr & 0x0f];
				if (cwidth != 9) {
				} else {
#ifdef TEST_FB
					vga_draw_glyph9_32(dst + cx * 36, 800 * 4, font_ptr, cheight, 0x0, 0xffffffff, 0);
#endif
				}
			}
			printf("%c", ch_attr & 0xff);
			src += 2;
			last_ch_attr++;
		}
		printf("\n");
		dst += 800 * 4 * cheight;
	}
}

